package updateinfoimpl

import (
	"encoding/xml"
	"fmt"
	"sort"
	"strings"

	"cvevulner/cve-ddd/domain"
	"cvevulner/cve-ddd/domain/dp"
	"cvevulner/util"
)

const (
	src     = "src"
	x8664   = "x86_64"
	aarch64 = "aarch64"
	noarch  = "noarch"

	// CveUrlPrefix is the prefix for CVE URLs
	CveUrlPrefix = "https://nvd.nist.gov/vuln/detail/"
)

var (
	severity = map[string]string{
		"critical": "Critical",
		"high":     "Important",
		"medium":   "Moderate",
		"low":      "Low",
	}
)

func (impl updateInfoImpl) UploadUpdateInfoXml(param domain.UpdateParam) (data []byte, err error) {
	up, err := impl.updateXml(param.Sb, param.Branch, param.Date)
	if err != nil {
		return nil, err
	}

	var u Updates
	err = xml.Unmarshal(param.DownloadBys, &u)
	if err != nil {
		return nil, err
	}

	i := impl.numberIndex(&u, param.Sb.Identification)
	if i == -1 {
		u.Updatex = append(u.Updatex, up)
	} else {
		if up.Description == "" {
			up.Description = u.Updatex[i].Description
		}
		if up.Title == "" {
			up.Title = u.Updatex[i].Title
		}
		u.Updatex[i] = up
	}

	sort.Slice(u.Updatex, func(i, j int) bool {
		return u.Updatex[i].Id < u.Updatex[j].Id
	})

	uploadBys, err := xml.MarshalIndent(u, "", "     ")
	if err != nil {
		return nil, err
	}

	headerBytes := []byte(xml.Header)
	headerBytes = append(headerBytes, uploadBys...)

	return headerBytes, nil
}

func (impl updateInfoImpl) updateXml(sb *domain.SecurityBulletin, branch, date string) (Update, error) {
	var cveNums []string
	var description string
	var highestLevelIndex int
	for _, cve := range sb.Cves {
		cveNums = append(cveNums, cve.CveNum)

		if description == "" {
			description = cve.ComponentDesc + "Security Fix(es):"
		}
		description += fmt.Sprintf("\n\n%s(%s)", cve.CveBrief, cve.CveNum)

		// Choose the highest security level in cves, as security level in bulletin
		for k, v := range dp.SequenceSeverityLevel {
			if v == cve.SeverityLevel && k > highestLevelIndex {
				highestLevelIndex = k
			}
		}
	}
	introduction := fmt.Sprintf("An update for %s is now available for", sb.Component)

	var descr string

	title := introduction + branch

	if sb.IsCveNotice() {
		if i := strings.Index(description, "Security Fix(es):"); i > 0 {
			descr = util.TrimStringNR(description[i+17:])
		}
	} else {
		descr = description
	}

	var up = Update{
		From:        "openeuler.org",
		Type:        "security",
		Status:      "stable",
		Id:          sb.Identification,
		Title:       title,
		Severity:    severity[strings.ToLower(dp.SequenceSeverityLevel[highestLevelIndex])],
		Release:     "openEuler",
		Issued:      &Issued{Date: sb.Date},
		Description: descr,
	}

	var ref []Reference
	for _, s := range cveNums {
		ref = append(ref, Reference{
			Href:  CveUrlPrefix + s,
			Id:    s,
			Title: s,
			Type:  "cve",
		})
	}

	up.References = &References{Reference: ref}

	var pack []Package
	for arch, pl := range sb.ProductTree {
		if arch == src {
			continue
		}

		// noarch的包在aarch64目录和x8664目录都存在，采用和官网一致的逻辑，从aarch64目录取
		if arch == noarch {
			arch = aarch64
		}

		epochOfRpm, err := impl.getEpochOfRpm(branch, date, arch)
		if err != nil {
			return Update{}, err
		}

		for _, productPackage := range pl {
			if productPackage.CPE != branch {
				continue
			}

			var pe Package
			pe.Filename = productPackage.FullName
			packVersionList := strings.Split(productPackage.FullName, "-")
			if len(packVersionList) >= 3 {
				pe.Version = packVersionList[len(packVersionList)-2]
				rpmName := packVersionList[len(packVersionList)-1][:len(packVersionList[len(packVersionList)-1])-4]
				lastIndex := strings.LastIndexAny(rpmName, ".")
				if lastIndex != -1 {
					pe.Release = rpmName[:lastIndex]
					pe.Arch = rpmName[lastIndex+1:]
				}
				pe.Name = strings.Join(packVersionList[0:len(packVersionList)-2], "-")
			}

			if !strings.Contains(pe.Filename, "kernel") {
				epoch, ok := epochOfRpm[pe.Filename]
				if ok {
					pe.Epoch = epoch
				}
			}

			pack = append(pack, pe)
		}
	}

	up.Pkglist = &Pkglist{Collection: &Collection{Name: "openEuler", Package: pack}}

	return up, nil
}

func (impl updateInfoImpl) numberIndex(u *Updates, securityNumber string) (index int) {
	index = -1
	for k, v := range u.Updatex {
		if strings.EqualFold(v.Id, securityNumber) {
			index = k
			return
		}
	}

	return
}
